using System;
using System.Collections.Generic;
using System.IO;
using System.Security;
using System.Text;

namespace Sandia.MVCFramework.Common
{
    /// <summary>
    /// A collection of utility functions used to work with directories and 
    /// files.
    /// </summary>
    public class FileSystemTools
    {
        /// <summary>
        /// Recursively walks the two given directories and compares them for
        /// equality. This function looks at both subdirectories and files.
        /// </summary>
        /// <param name="dirInfo1">A directory to compare.</param>
        /// <param name="dirInfo2">A directory to compare.</param>
        /// <returns>True if the directories are equal, false otherwise.</returns>
        public static bool CompareDirectoryInfos(
            DirectoryInfo dirInfo1,
            DirectoryInfo dirInfo2)
        {
            // Check input parameters.
            if (!dirInfo1.Exists)
            {
                LoggingTools.SendToLog(
                    "CompareDirectoryInfos: dirInfo1 does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return true;
            }

            if (!dirInfo2.Exists)
            {
                LoggingTools.SendToLog(
                    "CompareDirectoryInfos: dirInfo2 does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return true;
            }

            // Get the subdirectories.
            DirectoryInfo[] subdirectories1;
            DirectoryInfo[] subdirectories2;
            try
            {
                subdirectories1 = dirInfo1.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "CompareDirectoryInfos: User does not have access priviliges for the directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return true;
            }

            try
            {
                subdirectories2 = dirInfo2.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "CompareDirectoryInfos: User does not have access priviliges for the directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return true;
            }

#if true // ToDo, BST review these two sections with JT
            List<DirectoryInfo> missingDirInfos =
                ArrayTools<DirectoryInfo>.GetSubsetDifferences(subdirectories1,subdirectories2,new DirectoryInfoComparer());

            FileSystemInfo[] files1 = dirInfo1.GetFileSystemInfos();
            FileSystemInfo[] files2 = dirInfo2.GetFileSystemInfos();

            List<FileSystemInfo> missingFileInfos =
                ArrayTools<FileSystemInfo>.GetSubsetDifferences(files1, files2, new FileSystemInfoComparer());

            // If the subdirectories or the files are not equal, return false.
            if (missingDirInfos.Count > 0 || missingFileInfos.Count > 0)
            {
                return false;
            }
#else
            // If the subdirectories or the files are not equal, return false.
            if (!ArrayTools<DirectoryInfo>.CompareArrays(subdirectories1, subdirectories2, new DirectoryInfoComparer()) ||
                !ArrayTools<FileInfo>.CompareArrays(dirInfo1.GetFiles(), dirInfo2.GetFiles(), new FileInfoComparer()))
            {
                return false;
            }
#endif

            // Recursively compare the subdirectories.
            for (int i = 0; i < subdirectories1.Length; i++)
            {
                if (!CompareDirectoryInfos(subdirectories1[i], subdirectories2[i]))
                {
                    return false;
                }
            }

            // Stopping case, no differences found.
            return true;
        }

        /// <summary>
        /// Recursively walks the two given directories and compares them to 
        /// determine if the first is a subset of the second. This function 
        /// looks at both subdirectories and files.
        /// </summary>
        /// <param name="dirInfo1">
        /// The directory to check is contained by dirInfo2.
        /// </param>
        /// <param name="dirInfo2">
        /// The directory to check contains dirInfo1.
        /// </param>
        /// <returns>
        /// True if dirInfo1 is completely contained by dirInfo2, false 
        /// otherwise.
        /// </returns>
        public static bool IsSubset(
            DirectoryInfo dirInfo1,
            DirectoryInfo dirInfo2)
        {
            // Check input parameters.
            if (!dirInfo1.Exists)
            {
                LoggingTools.SendToLog(
                    "IsSubset: dirInfo1 does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return false;
            }

            if (!dirInfo2.Exists)
            {
                LoggingTools.SendToLog(
                    "IsSubset: dirInfo2 does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return false;
            }

            DirectoryInfo[] subdirectories1;
            DirectoryInfo[] subdirectories2;
            try
            {
                subdirectories1 = dirInfo1.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "IsSubset: User does not have access priviliges for the directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return false;
            }

            try
            {
                subdirectories2 = dirInfo2.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "IsSubset: User does not have access priviliges for the directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return false;
            }

            // If the subdirectories or the files are not equal, return false.
            if (!ArrayTools<DirectoryInfo>.IsSubset(subdirectories1, subdirectories2, new DirectoryInfoComparer()) ||
                !ArrayTools<FileInfo>.IsSubset(dirInfo1.GetFiles(), dirInfo2.GetFiles(), new FileInfoComparer()))
            {
                return false;
            }

            // Recursively compare the subdirectories.
            for (int i = 0; i < subdirectories1.Length; i++)
            {
                if (!IsSubset(subdirectories1[i], subdirectories2[i]))
                {
                    return false;
                }
            }

            // Stopping case, it is a subset.
            return true;
        }

        /// <summary>
        /// Synchronizes the directories and files from the given source 
        /// directory to the given target directory.
        /// </summary>
        /// <param name="sourceDirInfo">
        /// The source directory to copy the files from.
        /// </param>
        /// <param name="destinationDirInfo">
        /// The target directory to copy the files to.
        /// </param>
        /// <remarks>
        /// If the target directory does not exist, this function creates it.
        /// </remarks>
        public static void SynchronizeDirectories(
            DirectoryInfo sourceDirInfo,
            DirectoryInfo destinationDirInfo)
        {
            // Check input parameters.
            if (!sourceDirInfo.Exists)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: sourceDirInfo does not exist.",
                    LoggingCategories.ERROR,
                    100);
                return;
            }

            // If the destination directory doesn't exist, create it.
            if (!destinationDirInfo.Exists)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: destinationDirInfo does not exist, creating it.",
                    LoggingCategories.GENERAL,
                    50);
                try
                {
                    destinationDirInfo.Create();
                }
                catch (SecurityException e)
                {
                    LoggingTools.SendToLog(
                        "SynchronizeDirectories: User does not have access priviliges to create directory. \n" + e.Message,
                        LoggingCategories.ERROR,
                        100);
                    return;
                }
                catch (UnauthorizedAccessException e)
                {
                    LoggingTools.SendToLog(
                        "SynchronizeDirectories: User does not have access priviliges to create directory. \n" + e.Message,
                        LoggingCategories.ERROR,
                        100);
                    return;
                }
            }

            // Get lists of the sub-directories in each of the source and destination
            //
            DirectoryInfo[] sourceSubdirectories;
            DirectoryInfo[] destinationSubdirectories;
            try
            {
                sourceSubdirectories = sourceDirInfo.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges for the source directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return;
            }

            try
            {
                destinationSubdirectories = destinationDirInfo.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges for the destination directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
                return;
            }

            // Use the ArrayTools to determine the differences between the lists

            // Find the missing directories
            //
            List<DirectoryInfo> missingDirInfos =
                ArrayTools<DirectoryInfo>.GetSubsetDifferences(sourceSubdirectories, destinationSubdirectories, new DirectoryInfoComparer());

            // Find the missing files
            //
            List<FileInfo> missingFileInfos =
                ArrayTools<FileInfo>.GetSubsetDifferences(
                    sourceDirInfo.GetFiles(), 
                    destinationDirInfo.GetFiles(), 
                    new FileInfoComparer());

            // Copy the files from the source to the destination that are missing
            //
            try
            {
                foreach (FileInfo missingFileInfo in missingFileInfos)
                {
                    missingFileInfo.CopyTo(
                        Path.Combine(
                            destinationDirInfo.FullName,
                            missingFileInfo.Name), true);
                    FileInfo newFile = new FileInfo(Path.Combine(
                            destinationDirInfo.FullName,
                            missingFileInfo.Name));
                    newFile.CreationTime = missingFileInfo.CreationTime;
                }

                // Copy the directories from the source to the destination (recursively) that are missing
                //
                foreach (DirectoryInfo missingDirInfo in missingDirInfos)
                {
                    Copy(
                        missingDirInfo,
                        new DirectoryInfo(
                            Path.Combine(
                                destinationDirInfo.FullName,
                                missingDirInfo.Name)),
                                true);
                }
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges to copy. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }
            catch (UnauthorizedAccessException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges to copy. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }

            // Now, ensure that each sub-directory is synchronized (not just the missing ones)

            // Get the directory lists again
            //
            try
            {
                sourceSubdirectories = sourceDirInfo.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges for the source directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }

            try
            {
                destinationSubdirectories = destinationDirInfo.GetDirectories();
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "SynchronizeDirectories: User does not have access priviliges for the destination directory. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }

            int jj = -1; // index for destinationSubdirectories

            // Process the source directory list
            //
            for (int ii = 0; ii < sourceSubdirectories.Length ; ii++)
            {
                if (sourceSubdirectories[ii].Name.CompareTo("CVS") == 0) continue; // don't copy CVS directories

                jj++; // this shouldn't overflow the array bounds
    
                SynchronizeDirectories(sourceSubdirectories[ii], destinationSubdirectories[jj]);
            }
        }

        /// <summary>
        /// Recursively copies the given source directory to the given target 
        /// directory.
        /// </summary>
        /// <param name="source">The directory to copy.</param>
        /// <param name="target">The directory to copy to.</param>
        /// <param name="preserveAttributes">
        /// Indicates whether to preserve the file attributes when copying.
        /// </param>
        public static void Copy(
            DirectoryInfo source, 
            DirectoryInfo target,
            bool preserveAttributes)
        {
            // Check input parameters.
            if (!source.Exists)
            {
                LoggingTools.SendToLog(
                       "Copy: dirInfo1 does not exist.",
                       LoggingCategories.ERROR,
                       100);
            }

            // Check if the target directory exists, if not, create it.
            if (!target.Exists)
            {
                LoggingTools.SendToLog(
                       "Copy: target does not exist, creating it.",
                       LoggingCategories.GENERAL,
                       50);
                target.Create();
            }

            if (source.Name.CompareTo("CVS") == 0) return; // don't copy CVS directories

            // Copy each file into it's new directory.
            foreach (FileInfo fi in source.GetFiles())
            {
                fi.CopyTo(Path.Combine(target.ToString(), fi.Name), true); // overwrite the destination file if it exists
                if (preserveAttributes)
                {
                    FileInfo newFile = new FileInfo(Path.Combine(target.ToString(), fi.Name));
                    newFile.CreationTime = fi.CreationTime;
                }
            }

            // Copy each subdirectory using recursion.
            try
            {
                foreach (DirectoryInfo diSourceSubDir in source.GetDirectories())
                {
                    if (diSourceSubDir.Name.CompareTo("CVS") == 0) continue;  // don't copy CVS directories

                    DirectoryInfo nextTargetSubDir =
                        target.CreateSubdirectory(diSourceSubDir.Name);
                    Copy(diSourceSubDir, nextTargetSubDir, preserveAttributes);
                }
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "Copy: User does not have access priviliges to copy. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }
        }

        /// <summary>
        /// Deletes all files a folders from the given directory.
        /// </summary>
        /// <param name="dirInfo">The directory to clear.</param>
        public static void ClearDirectory(
            DirectoryInfo dirInfo)
        {
            // Check input parameters.
            if (!dirInfo.Exists)
            {
                LoggingTools.SendToLog(
                    "ClearDirectory: dirInfo does not exist.",
                    LoggingCategories.ERROR,
                    100);
            }

            // Loop through and delete each subdirectory.
            try
            {
                foreach (DirectoryInfo subDirectory in dirInfo.GetDirectories())
                {
                    subDirectory.Delete(true);
                }
                // Loop through and delete each file.
                foreach (FileInfo file in dirInfo.GetFiles())
                {
                    file.Delete();
                }
            }
            catch (SecurityException e)
            {
                LoggingTools.SendToLog(
                    "ClearDirectory: User does not have access priviliges to delete. \n" + e.Message,
                    LoggingCategories.ERROR,
                    100);
            }
        }

    }
}
